package ms.aurora.forms;

import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import ms.aurora.event.DefaultKeyListener;
import ms.aurora.event.DefaultMouseListener;
import ms.aurora.util.PairIterator;
import ms.aurora.util.Reflection;

import javax.swing.*;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import static ms.aurora.util.Reflection.Filters.fieldAnnotatedWith;

public class Form<T> extends JPanel {
    private static final long serialVersionUID = 1L;
    private final Map<String, Component> fieldComponentMap = Maps.newHashMap();
    private final JButton okButton = new JButton("Ok");
    private final JButton cancelButton = new JButton("Cancel");
    private final List<FormField> fields;
    private final Reflection modelReflection;
    private final T model;

    private FormHandler<T> formHandler;

    public Form(Class<T> modelClass, T model) {
        this.modelReflection = new Reflection(modelClass);
        this.model = model;
        this.fields = createForm();
        initComponents();
    }

    private void initComponents() {
        this.setLayout(new GridBagLayout());

        GridBagConstraints constraints = new GridBagConstraints();
        for (int i = 0; i < fields.size(); i++) {
            FormField curr = fields.get(i);
            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridx = 0;
            constraints.gridy = i;
            constraints.insets = new Insets(5, 5, 5, 10);
            this.add(curr.label, constraints);
            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.gridx = 1;
            constraints.gridy = i;
            constraints.ipadx = 100;
            this.add(curr.inputComponent, constraints);
        }

        JPanel buttonPanel = new JPanel();
        constraints.gridx = 1;
        constraints.gridy = fields.size();
        constraints.ipadx = 0;
        constraints.anchor = GridBagConstraints.WEST;
        buttonPanel.add(cancelButton);
        buttonPanel.add(okButton);
        this.add(buttonPanel, constraints);
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (formHandler != null) {
                    formHandler.onSubmit(model);
                }
            }
        });
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (formHandler != null) {
                    formHandler.onCancel(model);
                }
            }
        });
    }

    public T getModel() {
        return model;
    }

    public void setFormHandler(FormHandler<T> formHandler) {
        this.formHandler = formHandler;
    }

    private List<FormField> createForm() {
        PairIterator<Field, Annotation> inputPairs = extractFormPairs();
        List<FormField> resultingFields = Lists.newArrayList();
        while (inputPairs.next()) {
            final Input input = (Input) inputPairs.getSecond();
            final Field field = inputPairs.getFirst();
            try {
                resultingFields.add(getFormFieldByPair(input, field));
            } catch (Exception e) {
                throw new RuntimeException("Failed to create form field for "
                        + input.label());
            }
        }
        return resultingFields;
    }

    private FormField getFormFieldByPair(Input input, final Field field) throws Exception {
        FormField newField = new FormField();
        newField.label = new JLabel(input.label());
        newField.inputComponent = input.component().newInstance();
        newField.inputComponent.addKeyListener(new DefaultKeyListener() {
            @Override
            public void keyReleased(KeyEvent event) {
                pushValue(event.getSource(), field);
            }
        });
        newField.inputComponent.addMouseListener(new DefaultMouseListener() {
            @Override
            public void mouseReleased(MouseEvent event) {
                pushValue(event.getSource(), field);
            }
        });
        return newField;
    }

    private PairIterator<Field, Annotation> extractFormPairs() {
        Collection<Field> formFields = modelReflection
                .getFields(fieldAnnotatedWith(Input.class));
        Collection<Annotation> inputs = Collections2.transform(formFields,
                Reflection.Converters.fieldToAnnotation(Input.class));
        return new PairIterator<Field, Annotation>(formFields, inputs);
    }

    private void pushValue(Object source, Field field) {
        try {
            field.setAccessible(true);
            if (source instanceof JTextComponent) {
                field.set(model, ((JTextComponent) source).getText());
            } else if (source instanceof JSpinner) {
                field.set(model, ((JSpinner) source).getValue());
            }
        } catch (IllegalAccessException e1) {
            e1.printStackTrace();
        }
    }
}
